/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.dao.support.hbn;

import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.*;
import org.vacoor.nothing.common.reflect.GenericBeanSupport;

import java.io.Serializable;
import java.util.List;
import java.util.Map;


/**
 * 对 Hibernate API 的简单封装
 * 该类中的方法都是 protected 的, 之所以这样子搞是希望 DAO 对外暴露的 API 都是与具体框架无关的
 *
 * @author vacoor
 */
@SuppressWarnings({"unused", "unchecked"})
public abstract class HbnDaoSupport<E, ID extends Serializable> extends GenericBeanSupport<E> {
    protected final Class<E> entityType;

    /* ******************************
     *        BASIC API Method
     * ******************************/

    protected E get(ID id) {
        return (E) getSession().get(entityType, id);
    }

    protected E load(ID id) {
        return (E) getSession().load(entityType, id);
    }

    protected void save(E e) {
        getSession().save(e);
    }

    protected void update(E e) {
        getSession().update(e);
    }

    protected void saveOrUpdate(E e) {
        getSession().saveOrUpdate(e);
    }

    protected void delete(E e) {
        getSession().delete(e);
    }

    protected void evict(E e) {
        getSession().evict(e);
    }

    protected void flush() {
        getSession().flush();
    }

    protected void clear() {
        getSession().clear();
    }

    protected void initialize(Object proxy) {
        Hibernate.initialize(proxy);
    }

    protected boolean isInitialized(Object proxy) {
        return Hibernate.isInitialized(proxy);
    }

    protected boolean isPropertyInitialized(Object proxy, String propertyName) {
        return Hibernate.isPropertyInitialized(proxy, propertyName);
    }

    /* *************************
     *          QBC API
     * *************************/

    protected Criteria setParamsToCriteria(Criteria criteria, Order[] orders, int firstResult, int maxResults) {
        for (Order order : orders) {
            criteria.addOrder(order);
        }
        criteria.setFirstResult(firstResult);

        if (Integer.MAX_VALUE != maxResults) {
            criteria.setMaxResults(maxResults);
        }
        // return criteria;
        return distinctRootEntity(criteria);
    }

    protected Criteria distinctRootEntity(Criteria criteria) {
        return criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
    }

    protected E findOne(Criterion... criterions) {
        return findOne(buildCriteria(criterions));
    }

    protected List<E> findMany(Criterion... criterions) {
        return findMany(buildCriteria(criterions));
    }

    protected E findOne(Criteria criteria) {
        return (E) criteria.uniqueResult();
    }

    protected List<E> findMany(Criteria criteria) {
        return criteria.list();
    }

    /**
     * warn: {@link #findMany(Criteria, Order[], int, int)}
     */
    protected E fineOne(Criteria criteria, Order[] orders, int firstResult, int maxResults) {
        return (E) setParamsToCriteria(criteria, orders, firstResult, maxResults).uniqueResult();
    }

    /**
     * warn: maxResults 是结果数量, m:n(n > 1) 级联查询(fetch = EAGER) 时, 会存在以下问题
     * user:role m:n
     * user1 -- role1, role2, role3
     * 当 maxResults = 2 可能结果: user1 -- role1, user1 -- role2
     * 但实际可能是想查询 2 个用户, 而真正查询出来是 1 个用户
     * 另外在封装时也是 [user1 (role1, role2), user1(role1, role2)] (该情况可用 distinctRootEntity解决)
     * 因此建议 m:n (n > 1) 不要设置 fetch = EAGER, 而通过 lazyLoad 与 Hibernate.initialize(proxy) 来处理
     */
    protected List<E> findMany(Criteria criteria, Order[] orders, int firstResult, int maxResults) {
        return setParamsToCriteria(criteria, orders, firstResult, maxResults).list();
    }

    /**
     * {@link #count(org.hibernate.Criteria)}
     */
    protected int count(Criterion... criterions) {
        return count(buildCriteria(criterions));
    }

    /**
     * 注意该方法会修改 Projection
     */
    protected int count(Criteria criteria) {
        /* 这种方式没有办法去重
        criteria.setResultTransformer(DistinctResultTransformer.INSTANCE);
        return ((Number) criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
        */
        return ((Number) criteria.setProjection(Projections.countDistinct(getIdentifierName())).uniqueResult()).intValue();
    }

    /* ****************************************
     *        QBC - DetachedCriteria API
     * ****************************************/

    protected E findOne(DetachedCriteria detachedCriteria) {
        return findOne(detachedCriteria.getExecutableCriteria(getSession()));
    }

    protected E findOne(DetachedCriteria detachedCriteria, Order[] orders, int firstResult, int maxResults) {
        return fineOne(detachedCriteria.getExecutableCriteria(getSession()), orders, firstResult, maxResults);
    }

    protected List<E> findMany(DetachedCriteria detachedCriteria) {
        return detachedCriteria.getExecutableCriteria(getSession()).list();
    }

    protected List<E> findMany(DetachedCriteria detachedCriteria, Order[] orders, int firstResult, int maxResults) {
        return findMany(detachedCriteria.getExecutableCriteria(getSession()), orders, firstResult, maxResults);
    }

    protected int count(DetachedCriteria detachedCriteria) {
        return count(detachedCriteria.getExecutableCriteria(getSession()));
    }

    /* **********************************
     *           HQL Query API
     * **********************************/

    protected <T> T findOne(String hql, Object... values) {
        return (T) buildQuery(hql, values).uniqueResult();
    }

    protected <T> List<T> findMany(String hql, Object... values) {
        return buildQuery(hql, values).list();
    }

    protected <T> List<T> findMany(String hql, int firstResult, int maxResults, Object... values) {
        Query query = buildQuery(hql, values);
        return query.setFirstResult(firstResult).setMaxResults(maxResults).list();
    }

    protected <T> T findOne(String hql, Map<String, ?> values) {
        return (T) buildQuery(hql, values).uniqueResult();
    }

    protected <T> List<T> findMany(String hql, Map<String, ?> values) {
        return buildQuery(hql, values).list();
    }

    protected <T> List<T> findMany(String hql, int firstResult, int maxResults, Map<String, ?> values) {
        Query query = buildQuery(hql, values);
        return query.setFirstResult(firstResult).setMaxResults(maxResults).list();
    }

    protected int executeUpdate(String hql, Object... values) {
        return buildQuery(hql, values).executeUpdate();
    }

    protected int executeUpdate(String hql, Map<String, ?> values) {
        return buildQuery(hql, values).executeUpdate();
    }

    /* *************************
     *         Builder
     * *************************/

    protected Criteria buildCriteria(Criterion... criterions) {
        return buildCriteria(CriteriaSpecification.ROOT_ALIAS, criterions);
    }

    protected Criteria buildCriteria(String alias, Criterion... criterions) {
        Criteria criteria = getSession().createCriteria(entityType, alias);

        for (Criterion criterion : criterions) {
            criteria.add(criterion);
        }
        return criteria;
    }

    protected DetachedCriteria buildDetachedCriteria(Criterion... criterions) {
        return buildDetachedCriteria(CriteriaSpecification.ROOT_ALIAS, criterions);
    }

    protected DetachedCriteria buildDetachedCriteria(String alias, Criterion... criterions) {
        DetachedCriteria dc = DetachedCriteria.forClass(entityType, alias);

        for (Criterion criterion : criterions) {
            dc.add(criterion);
        }
        return dc;
    }

    protected Query buildQuery(String hql, Object... values) {
        Query query = getSession().createQuery(hql);
        for (int i = 0; i < values.length; i++) {
            query.setParameter(i, values[i]);
        }
        return query;
    }

    protected Query buildQuery(String hql, Map<String, ?> values) {
        Query query = getSession().createQuery(hql);
        if (null != values) {
            query.setProperties(values);
        }
        return query;
    }

    protected abstract Session getSession();

    protected abstract String getIdentifierName();

    // ----------------------

    protected HbnDaoSupport() {
        entityType = getActualTypeArgument();
    }
}
